跳到主要内容

MutationObserve 例子

为了防止大家对 MutationObserve 没有概念,我们归纳一些场景并总结出一些例子,方便大家更好的理解

子元素的监听修改

HTML 的格式为

<body>
<div class="target"></div>
</body>

代码为

const target = document.querySelector(".target");
let observer = new MutationObserver(function (mutations) {
console.log("mutations", mutations);
});
observer.observe(target, {
childList: true,
});
const childList1 = document.createElement("div");
childList1.innerText = "div1";
target.append(childList1);
const childList2 = document.createElement("div");
childList2.innerText = "div2";
target.append(childList2);
childList2.remove();
childList1.remove();

最终输出为

mutations Array(4) [ MutationRecord, MutationRecord, MutationRecord, MutationRecord ]

我们可以看到出现了四次 Record,分别是下面四行代码触发的

target.append(childList1);
target.append(childList2);
childList2.remove();
childList1.remove();

我们依次看每个 Record

第一个 Record

数据如下

addedNodes: NodeList [ div ],
attributeName: null,
attributeNamespace: null,
nextSibling: null,
oldValue: null,
previousSibling: null,
removedNodes: NodeList [],
target: <div class="target">,
type: "childList"

根据 type 为 childList 表示子元素发生变化,target 表示对哪个元素所进行的操作,其他的属性我们只需要关注 removedNodes/addedNodes,以及 previousSibling/nextSibling 四个属性。

其中 removedNodes 没有任何内容,表示没有删除任何元素,而 addedNodes 有一个 DOM 元素,是我们通过target.append(childList1)插入一个 childList1 元素所触发的,previousSibling 以及 nextSibling 都为 null,表示相邻的兄弟元素都不存在。

第二个 Record

数据如下

addedNodes: NodeList [ div ]
attributeName: null
attributeNamespace: null
nextSibling: null
oldValue: null
previousSibling: <div>
removedNodes: NodeList []
target: <div class="target">
type: "childList"

这里与上述基本一致,有所区别的是 previousSibling 不在为 null,因为第一次 Record 插入一个 div 后,我们又进行一次 append,此时插入的元素的相邻上一个元素,刚好是第一次 Record 的元素,结构如下

<div class="target">
//第一次Record插入的元素
<div>div1</div>
//第二次Record插入的元素,此时相邻的上一个兄弟元素为第一次插入的元素
<div>div2</div>
</div>

第三个 Record

数据如下

addedNodes: NodeList []
attributeName: null
attributeNamespace: null
nextSibling: null
oldValue: null
previousSibling: <div>
removedNodes: NodeList [ div ]
target: <div class="target">
type: "childList"

这里对应了代码childList2.remove();,因为也属于子元素的增删改查,所以 type 依然是 childList,但是这行代码仅仅做了删除操作,所以 addedNodes 为空,removedNodes 表示删除了哪个元素,而 previousSibling 表示删除的元素节点的相邻兄弟节点的上一个节点,结构图如下

<div class="target">
//相邻兄弟节点的上一个节点
<div>div1</div>
//删除该元素
<div>div2</div>
</div>

第四个 Record

数据如下

addedNodes: NodeList []
attributeName: null
attributeNamespace: null
nextSibling: null
oldValue: null
previousSibling: null
removedNodes: NodeList [ div ]
target: <div class="target">
type: "childList"

删除 div1 元素,此时 div2 已经被删除,所以 previousSibling 和 nextSibling 都为 null

<div class="target">
//删除该元素
<div>div1</div>
</div>

元素的属性监听

在该部分我们将尝试修改元素的属性来触发 MutaitonObserve 监听

HTML 的格式为

<body>
<div class="target"></div>
</body>

代码为

const target = document.querySelector(".target");
let observer = new MutationObserver(function (mutations) {
console.log("mutations", mutations);
});
observer.observe(target, {
attributes: true,
attributeOldValue: true,
});
target.setAttribute("env", "test");
target.removeAttribute("env");

最终输出为

mutations Array [ MutationRecord, MutationRecord ]

我们可以看到出现了两次 Record,分别是下面两行代码触发的

target.setAttribute("env", "test");
target.removeAttribute("env");

我们依次看每个 Record

第一个 Record

数据如下

addedNodes: NodeList []
attributeName: "env"
attributeNamespace: null
nextSibling: null
oldValue: null
previousSibling: null
removedNodes: NodeList []
target: <div class="target">
type: "attributes"

这里可以看到 type 变成了 attributes,表示属性发生了修改,而 target 表示哪个元素的属性发生了修改,因为只是修改属性,所以我们只需要着重观察 attributeName/oldValue 即可。

attributeNamespace 属性表示命名空间,通常调用 setAttributeNS 等函数表示对应的命名空间才会显示,通常使用频率较低,如果读者感兴趣可以自行查阅资料。

attributeName 表示修改的属性名是哪个,因为我们设置的是 env 属性,所以 attributeName 显示了 env。

oldValue 表示在修改前的属性值是什么,因为我们是初次设置,所以为 null。

oldValue 显示 attributeName 的上一次修改值,如需显示必须在调用 observe 时将 attributeOldValue 设置为 true。

第二个 Record

数据如下

addedNodes: NodeList []
attributeName: "env"
attributeNamespace: null
nextSibling: null
oldValue: "test"
previousSibling: null
removedNodes: NodeList []
target: <div class="target">
type: "attributes"

该 Record 是由代码target.removeAttribute("env");所触发的,这里我们删除了属性。

与第一个有所不同的是 oldValue 为 test,表示在删除前 env 属性的值为"test",其他基本一致。

元素的后代节点监听

如果不将 subtree 设置为 true,则监听的对象仅限于传入的 target,当 subtree 为 true,则监听 target 下的所有元素

例如,仅设置 childList 为 true,则监听 target 的子元素的变化,而设置 childList 以及 subtree 都为 true,则 target 下的子树(即包含 target 在内的所有元素),都将被观察子元素的变化,attributes,characterData 同理。

举个例子

HTML 的格式为

<body>
<div class="target">
<div class="son">
<div class="grandson"></div>
</div>
</div>
</body>

代码为

const target = document.querySelector(".target");
const grandson = document.querySelector(".grandson");
let observer = new MutationObserver(function (mutations) {
console.log("mutations", mutations);
});
observer.observe(target, {
childList: true,
subtree: true,
});
const Computer = document.createElement("div");
Computer.innerText = "Grandchild's Computer";
grandson.append(Computer);

最终输出为

mutations Array [ MutationRecord ]

如果我们不设置 subtree 为 true,只能监听到 target 元素的子元素的变化,此时无法监听到 grandson 的子元素的改变。

当设置了 subtree 为 true,以及 childList 为 true,才可以监听到 grandson 的子元素发生变化。

元素的字符数据监听

HTML 的格式为

<body>
<div class="target">text</div>
</body>

代码为

const target = document.querySelector(".target");
let observer = new MutationObserver(function (mutations) {
console.log("mutations", mutations);
});
observer.observe(target, {
characterData: true,
characterDataOldValue: true,
subtree: true,
});
target.childNodes[0].textContent = "text1";
target.childNodes[0].textContent = "text2";

最终输出为

mutations Array [ MutationRecord, MutationRecord ]

我们可以看到出现了两次 Record,分别是下面两行代码触发的

target.childNodes[0].textContent = "text1";
target.childNodes[0].textContent = "text2";

我们依次看每个 Record

第一个 Record

addedNodes: NodeList []
attributeName: null
attributeNamespace: null
nextSibling: null
oldValue: "text"
previousSibling: null
removedNodes: NodeList []
target: #text "text2"
type: "characterData"

根据 type 为 characterData 表示是元素的文本数据子元素的修改操作,target 表示对哪个元素所进行的操作,这里是一个文本元素,属性只需要观察 oldValue,表示修改前的数据,这里为"text"

元素下的文字也属于一个独立元素,如果不设置 subtree 为 true,表示不监听子代的所有元素,将无法触发,所以必须将 subtree 设置为 true。

oldValue 显示 characterData 的上一次修改值,如需显示必须在调用 observe 时将 characterDataOldValue 设置为 true。

第二个 Record

addedNodes: NodeList []
attributeName: null
attributeNamespace: null
nextSibling: null
oldValue: "text1"
previousSibling: null
removedNodes: NodeList []
target: #text "text2"
type: "characterData"

与上一个 Record 一致,唯一区别是 oldValue 因为是第二次修改,所以值为 text1。